﻿using System;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

namespace Wicresoft.Shell.Package.AppExp
{
    public class XmlUmcReader : XmlReader
    {
        #region PrivateClass

        private class NodeData
        {
            internal XmlNodeType NodeType;
            internal String Name;
            internal String Value;
            internal NameValueCollection Attributes;
            internal Int32 CurrentAttribute;
            internal Boolean IsEnd;

            internal NodeData()
            {
                NodeType = XmlNodeType.None;
                Name = String.Empty;
                Value = String.Empty;
                Attributes = new NameValueCollection();
                CurrentAttribute = -1;
                IsEnd = false;
            }
        }

        #endregion

        #region Fields

        private static Regex mS_UmcElemNameRegex;
        private static Regex mS_UmcElemAttrRegex;

        private TextReader m_BaseReader;
        private ReadState m_ReadState;

        private String m_XmlElemName;
        private String m_NameVal;
        private String m_ValueVal;
        private NameValueCollection m_AttributeCol;
        private Int32 m_AttributeIdx;
        private XmlNodeType m_NodeType;
        private Boolean m_ElemIsEnd;
        private Stack m_XmlElemNameStack;
        private NameTable m_NameTable;

        #endregion

        #region Constructor

        static XmlUmcReader()
        {
            RegexOptions regexOpt = RegexOptions.Compiled;
            String umcElemNameRegex = "^\\s*<(/)?([a-zA-Z]\\w*)\\s*";
            String umcElemAttrRegex = "^\\s*([a-zA-Z]\\w*)\\s*=\\s*\"([^\"]*)\"\\s*";

            mS_UmcElemNameRegex = new Regex(umcElemNameRegex, regexOpt);
            mS_UmcElemAttrRegex = new Regex(umcElemAttrRegex, regexOpt);
        }

        public XmlUmcReader(TextReader textReader)
        {
            Debug.Assert(null != textReader, "Pass in an invalid 'textReader'");

            m_BaseReader = textReader;
            m_ReadState = ReadState.Initial;

            m_XmlElemName = String.Empty;
            m_NameVal = String.Empty;
            m_ValueVal = String.Empty;
            m_AttributeCol = new NameValueCollection();
            m_AttributeIdx = -1;
            m_NodeType = XmlNodeType.None;
            m_ElemIsEnd = false;
            m_XmlElemNameStack = new Stack();
            m_NameTable = new NameTable(); ;
        }

        #endregion

        #region PrivateMethods

        private NodeData ReadUmcElem(String umcElemStr)
        {
            Debug.Assert(!String.IsNullOrEmpty(umcElemStr));

            NodeData newNode = new NodeData();
            Int32 chIdx = 0;
            Match match = null;

            newNode.NodeType = XmlNodeType.Element;

            // Read element name
            match = mS_UmcElemNameRegex.Match(umcElemStr);
            if (match.Success)
            {
                newNode.Name = m_NameTable.Add(match.Groups[2].Value);
                if (match.Groups[1].Success)
                {
                    newNode.NodeType = XmlNodeType.EndElement;
                    newNode.IsEnd = true;
                }
                chIdx = match.Length;
            }
            else
            {
                throw new Exception("Invalid umc element.");
            }

            // Read element attribute
            while (Char.IsLetter(umcElemStr, chIdx))
            {
                match = mS_UmcElemAttrRegex.Match(umcElemStr, chIdx, (umcElemStr.Length - chIdx));
                if (match.Success)
                {
                    newNode.Attributes.Add(m_NameTable.Add(match.Groups[1].Value),
                                           match.Groups[2].Value);
                    chIdx += match.Length;
                }
            }

            if ('/' == umcElemStr[chIdx])
                newNode.IsEnd = true;

            return newNode;
        }

        private NodeData ReadCurUmcElem(StringBuilder strBuffer)
        {
            Debug.Assert(null != strBuffer);

            Int32 curChVal = 0;
            Char curCh = Char.MinValue;

            while ((curChVal = m_BaseReader.Read()) != -1)
            {
                curCh = Convert.ToChar(curChVal);
                strBuffer.Append(curCh);
                if ('>' == curCh)
                    break;
            }
            if (-1 == curChVal)
                throw new Exception("Invalid umc element.");

            return ReadUmcElem(strBuffer.ToString());
        }

        private NodeData ReadVelocity(String velocityStr)
        {
            Debug.Assert(!String.IsNullOrEmpty(velocityStr));

            NodeData newNode = new NodeData();

            newNode.NodeType = XmlNodeType.Element;
            newNode.Name = m_NameTable.Add("VelocityCode");
            newNode.Attributes.Add(m_NameTable.Add("Code"),
                                   velocityStr);
            newNode.IsEnd = true;

            return newNode;
        }

        private NodeData ReadCurVelociry(StringBuilder strBuffer)
        {
            Int32 lastLineStartIdx = 0;
            Int32 curChVal = 0;
            Char curCh = Char.MinValue;

            if ((curChVal = m_BaseReader.Read()) != -1)
            {
                curCh = Convert.ToChar(curChVal);
                strBuffer.Append(curCh);
                if ('*' != curCh)
                {
                    strBuffer.Append(m_BaseReader.ReadLine());
                    lastLineStartIdx = strBuffer.Length;
                }
                else
                {
                    // Find mutil-line comment end
                    Boolean gotStar = false;

                    while ((curChVal = m_BaseReader.Read()) != -1)
                    {
                        curCh = Convert.ToChar(curChVal);
                        strBuffer.Append(curCh);
                        if (gotStar)
                        {
                            if ('#' == curCh)
                                break;
                        }
                        else
                        {
                            gotStar = ('*' == curCh);
                        }
                    }
                }
            }

            return ReadVelocity(strBuffer.ToString());
        }

        private NodeData ReadTextNode(String textStr)
        {
            Debug.Assert(null != textStr);

            NodeData newNode = new NodeData();

            newNode.NodeType = XmlNodeType.Text;
            newNode.Value = textStr;
            newNode.IsEnd = true;

            return newNode;
        }

        private NodeData ReadCurTextNode(StringBuilder strBuffer)
        {
            Debug.Assert(null != strBuffer);

            Int32 curChVal = 0;
            Char curCh = Char.MinValue;

            while ((curChVal = m_BaseReader.Peek()) != -1)
            {
                curCh = Convert.ToChar(curChVal);
                if ('<' != curCh && '#' != curCh)
                {
                    strBuffer.Append(curCh);
                    m_BaseReader.Read();
                }
                else
                {
                    break;
                }
            }
            if (-1 == curChVal)
                throw new Exception("Invalid text element");

            return ReadTextNode(strBuffer.ToString());
        }

        private void SetReaderByAttributeIdx(Int32 attrIdx)
        {
            Debug.Assert(attrIdx < m_AttributeCol.Count);

            m_NodeType = XmlNodeType.Attribute;
            m_AttributeIdx = attrIdx;
            m_NameVal = m_AttributeCol.Keys[attrIdx];
            m_ValueVal = m_AttributeCol[m_NameVal];
        }

        #endregion

        #region PublicMethods

        public override void Close()
        {
            if (System.Xml.ReadState.Closed != m_ReadState)
            {
                m_ReadState = System.Xml.ReadState.Closed;
                m_BaseReader.Close();
            }
        }

        public override String GetAttribute(Int32 i)
        {
            // An 'ArgumentOutofRangeException' will be throw if 'i' is a invalid index value.
            // Which is same with the definition of 'XmlReader.GetAttribute'.
            String attrName = m_AttributeCol.Keys[i];

            return (m_AttributeCol[attrName]);
        }

        public override String GetAttribute(String name, String namespaceURI)
        {
            return GetAttribute(name);
        }

        public override String GetAttribute(String name)
        {
            // NameValueCollection.Items[] will return 'null' if specified key is not existing.
            // Which is same with the definition of 'XmlReader.GetAttribute'.
            return (m_AttributeCol[name]);
        }

        public override string LookupNamespace(string prefix)
        {
            throw new NotImplementedException();
        }

        public override Boolean MoveToAttribute(String name, String ns)
        {
            return MoveToAttribute(name);
        }

        public override Boolean MoveToAttribute(String name)
        {
            if (m_AttributeCol.Count > 0)
            {
                for (Int32 attrIdx = 0; attrIdx < m_AttributeCol.Count; ++attrIdx)
                {
                    if (String.Equals(name, m_AttributeCol.Keys[attrIdx], StringComparison.Ordinal))
                    {
                        SetReaderByAttributeIdx(attrIdx);

                        return true;
                    }
                }
            }

            return false;
        }

        public override Boolean MoveToElement()
        {
            if (-1 != m_AttributeIdx)
            {
                m_AttributeIdx = -1;
                m_NameVal = m_XmlElemName;
                m_ValueVal = String.Empty;
                m_NodeType = XmlNodeType.Element;

                return true;
            }

            return false;
        }

        public override Boolean MoveToFirstAttribute()
        {
            if (m_AttributeCol.Count > 0)
            {
                SetReaderByAttributeIdx(0);

                return true;
            }

            return false;
        }

        public override Boolean MoveToNextAttribute()
        {
            if ((m_AttributeIdx + 1) < m_AttributeCol.Count)
            {
                SetReaderByAttributeIdx(m_AttributeIdx + 1);

                return true;
            }

            return false;
        }

        public override Boolean Read()
        {
            Debug.Assert((System.Xml.ReadState.Closed != m_ReadState), "Cannot read some more node on a closed 'XmlUmcReader'.");

            Boolean gotNewNode = false;

            if (ReadState.EndOfFile != m_ReadState)
            {
                String curLine = String.Empty;
                Int32 curChVal = 0;
                Char curCh = Char.MinValue;
                StringBuilder strBuffer = new StringBuilder();
                NodeData newNode = null;

                m_ReadState = System.Xml.ReadState.Interactive;

                // Find first non-space char
                while ((curChVal = m_BaseReader.Read()) != -1)
                {
                    curCh = Convert.ToChar(curChVal);
                    if (!Char.IsWhiteSpace(curCh))
                    {
                        strBuffer.Append(curCh);
                        break;
                    }
                }
                if (-1 == curChVal)
                {
                    m_ReadState = ReadState.EndOfFile;
                    goto EndRead;
                }

                if ('<' == curCh)
                {
                    newNode = ReadCurUmcElem(strBuffer);
                    gotNewNode = true;
                }
                else if ('#' == curCh)
                {
                    newNode = ReadCurVelociry(strBuffer);
                    gotNewNode = true;
                }
                else if (Char.IsLetterOrDigit(curCh))
                {
                    newNode = ReadCurTextNode(strBuffer);
                    gotNewNode = true;
                }
                else
                {
                    throw new Exception("Invalid char.");
                }

                // Push new node into node stack
                if (newNode.IsEnd)
                {
                    if (XmlNodeType.EndElement == newNode.NodeType)
                    {
                        if (Object.ReferenceEquals(newNode.Name, m_XmlElemNameStack.Peek()))
                            m_XmlElemNameStack.Pop();
                        else
                            throw new Exception("Un-match node.");
                    }
                }
                else
                {
                    m_XmlElemNameStack.Push(newNode.Name);
                }

                // Set reader value
                m_NodeType = newNode.NodeType;
                m_AttributeCol = newNode.Attributes;
                m_AttributeIdx = -1;
                m_ElemIsEnd = newNode.IsEnd;
                switch (m_NodeType)
                {
                    case XmlNodeType.Element:
                    case XmlNodeType.EndElement:
                        m_XmlElemName = newNode.Name;
                        m_NameVal = m_XmlElemName;
                        m_ValueVal = String.Empty;
                        break;
                    case XmlNodeType.Text:
                        m_XmlElemName = String.Empty;
                        m_NameVal = String.Empty;
                        m_ValueVal = newNode.Value;
                        break;
                    default:
                        throw new Exception("Invalid node.");
                }
            }
        EndRead:

            return gotNewNode;
        }

        public override Boolean ReadAttributeValue()
        {
            if (XmlNodeType.Attribute == m_NodeType)
            {
                m_NodeType = XmlNodeType.Text;
                m_NameVal = String.Empty;

                return true;
            }

            return false;
        }

        public override void ResolveEntity()
        {
            throw new NotImplementedException();
        }

        #endregion

        #region PublicProperties

        public override Int32 AttributeCount
        {
            get { return m_AttributeCol.Count; }
        }

        public override String BaseURI
        {
            get { return (String.Empty); }
        }

        public override int Depth
        {
            get { throw new NotImplementedException(); }
        }

        public override Boolean EOF
        {
            get { return (ReadState.EndOfFile == m_ReadState); }
        }

        public override Boolean HasValue
        {
            get { return (!String.IsNullOrEmpty(m_ValueVal)); }
        }

        public override Boolean IsEmptyElement
        {
            get { return (XmlNodeType.Element == m_NodeType && m_ElemIsEnd); }
        }

        public override String LocalName
        {
            get { return m_NameVal; }
        }

        public override XmlNameTable NameTable
        {
            get { return m_NameTable; }
        }

        public override String NamespaceURI
        {
            get { return (String.Empty); }
        }

        public override XmlNodeType NodeType
        {
            get { return m_NodeType; }
        }

        public override String Prefix
        {
            get { return (String.Empty); }
        }

        public override ReadState ReadState
        {
            get { return m_ReadState; }
        }

        public override String Value
        {
            get { return m_ValueVal; }
        }

        #endregion
    }
}
